advent_of_code/year2023/
day14.rs1use crate::common::array_stack::ArrayStack;
2use crate::input::Input;
3use std::collections::hash_map::DefaultHasher;
4use std::hash::{Hash, Hasher};
5
6pub fn solve(input: &Input) -> Result<u64, String> {
7 let mut moving = ArrayStack::<128, u128>::new();
8 let mut fixed = ArrayStack::<128, u128>::new();
9 let mut num_cols = 0;
10
11 for row_str in input.text.lines() {
12 let mut moving_bits = 0;
13 let mut fixed_bits = 0;
14 for (col_idx, b) in row_str.bytes().enumerate() {
15 num_cols = num_cols.max(col_idx + 1);
16 match b {
17 b'#' => {
18 fixed_bits |= 1 << col_idx;
19 }
20 b'O' => {
21 moving_bits |= 1 << col_idx;
22 }
23 _ => {}
24 }
25 }
26 moving.push(moving_bits)?;
27 fixed.push(fixed_bits)?;
28 }
29
30 if input.is_part_one() {
31 for x in 0..num_cols {
32 move_dir(0, 1, x, 0, num_cols, moving.slice_mut(), fixed.slice());
33 }
34 return Ok(total_load(moving.slice()));
35 }
36
37 let mut hashes = ArrayStack::<256, u64>::new();
38 hashes.push(calculate_hash(moving.slice()))?;
39
40 let mut remaining = 1_000_000_000;
41 loop {
42 for x in 0..num_cols {
43 move_dir(0, 1, x, 0, num_cols, moving.slice_mut(), fixed.slice());
44 }
45 for y in 0..moving.len() {
46 move_dir(1, 0, 0, y, num_cols, moving.slice_mut(), fixed.slice());
47 }
48 for x in 0..num_cols {
49 move_dir(
50 0,
51 -1,
52 x,
53 moving.len() - 1,
54 num_cols,
55 moving.slice_mut(),
56 fixed.slice(),
57 );
58 }
59 for y in 0..moving.len() {
60 move_dir(
61 -1,
62 0,
63 num_cols - 1,
64 y,
65 num_cols,
66 moving.slice_mut(),
67 fixed.slice(),
68 );
69 }
70 let hash = calculate_hash(moving.slice());
71 remaining -= 1;
72 for i in 0..hashes.len() {
73 if hashes.elements[i] == hash {
74 let cycle_length = hashes.len() - i;
75 remaining %= cycle_length;
76 }
77 }
78 hashes.push(hash)?;
79 if remaining == 0 {
80 return Ok(total_load(moving.slice()));
81 }
82 }
83}
84
85fn move_dir(
86 x_dir: i32,
87 y_dir: i32,
88 mut x: usize,
89 mut y: usize,
90 num_cols: usize,
91 moving: &mut [u128],
92 fixed: &[u128],
93) {
94 let mut position_at = (x, y);
95
96 loop {
97 if (moving[y] & (1 << x)) != 0 {
98 moving[y] &= !(1 << x);
99 moving[position_at.1] |= 1 << position_at.0;
100 position_at = (
101 (position_at.0 as i32 + x_dir) as usize,
102 (position_at.1 as i32 + y_dir) as usize,
103 );
104 } else if (fixed[y] & (1 << x)) != 0 {
105 position_at = ((x as i32 + x_dir) as usize, (y as i32 + y_dir) as usize);
106 }
107
108 if (x_dir == -1 && x == 0)
109 || (y_dir == -1 && y == 0)
110 || (x_dir == 1 && x == num_cols - 1)
111 || (y_dir == 1 && y == moving.len() - 1)
112 {
113 break;
114 }
115 x = (x as i32 + x_dir) as usize;
116 y = (y as i32 + y_dir) as usize;
117 }
118}
119
120fn total_load(moving: &[u128]) -> u64 {
121 moving
122 .iter()
123 .rev()
124 .enumerate()
125 .map(|(idx, row)| (idx + 1) as u64 * u64::from(row.count_ones()))
126 .sum()
127}
128
129fn calculate_hash(t: &[u128]) -> u64 {
130 let mut s = DefaultHasher::new();
131 t.hash(&mut s);
132 s.finish()
133}
134
135#[test]
136pub fn tests() {
137 use crate::input::{test_part_one_no_allocations, test_part_two_no_allocations};
138
139 let test_input = "O....#....
140O.OO#....#
141.....##...
142OO.#O....O
143.O.....O#.
144O.#..O.#.#
145..O..#O..O
146.......O..
147#....###..
148#OO..#....";
149 test_part_one_no_allocations!(test_input => 136);
150 test_part_two_no_allocations!(test_input => 64);
151
152 let real_input = include_str!("day14_input.txt");
153 test_part_one_no_allocations!(real_input => 108_641);
154 test_part_two_no_allocations!(real_input => 84_328);
155
156 let real_input = include_str!("day14_input_other.txt");
157 test_part_two_no_allocations!(real_input => 91_286);
158}