Skip to content

Commit f53bd70

Browse files
committed
Faster heuristics and multithreading
1 parent f2d8986 commit f53bd70

File tree

2 files changed

+77
-11
lines changed

2 files changed

+77
-11
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pie
109109
| 20 | [Pulse Propagation](https://adventofcode.com/2023/day/20) | [Source](src/year2023/day20.rs) | 6 |
110110
| 21 | [Step Counter](https://adventofcode.com/2023/day/21) | [Source](src/year2023/day21.rs) | 180 |
111111
| 22 | [Sand Slabs](https://adventofcode.com/2023/day/22) | [Source](src/year2023/day22.rs) | 486 |
112-
| 23 | [A Long Walk](https://adventofcode.com/2023/day/23) | [Source](src/year2023/day23.rs) | 89000 |
112+
| 23 | [A Long Walk](https://adventofcode.com/2023/day/23) | [Source](src/year2023/day23.rs) | 3181 |
113113
| 24 | [Never Tell Me The Odds](https://adventofcode.com/2023/day/24) | [Source](src/year2023/day24.rs) | 96 |
114114
| 25 | [Snowverload](https://adventofcode.com/2023/day/25) | [Source](src/year2023/day25.rs) | 157 |
115115

src/year2023/day23.rs

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ use crate::util::grid::*;
22
use crate::util::hash::*;
33
use crate::util::point::*;
44
use std::collections::VecDeque;
5+
use std::sync::atomic::{AtomicU32, Ordering};
6+
use std::thread;
57

68
pub struct Input {
9+
start: usize,
10+
end: usize,
11+
extra: u32,
712
directed: [u64; 36],
813
undirected: [u64; 36],
914
weight: [[u32; 36]; 36],
@@ -47,8 +52,8 @@ pub fn parse(input: &str) -> Input {
4752

4853
// BFS to find distances between POIs.
4954
let mut todo = VecDeque::new();
50-
let mut directed = [0; 36];
51-
let mut undirected = [0; 36];
55+
let mut directed: [u64; 36] = [0; 36];
56+
let mut undirected: [u64; 36] = [0; 36];
5257
let mut weight = [[0; 36]; 36];
5358

5459
for (&start, &from) in &poi {
@@ -90,14 +95,34 @@ pub fn parse(input: &str) -> Input {
9095
}
9196
}
9297

93-
Input { directed, undirected, weight }
98+
// Compress
99+
let start = undirected[0].trailing_zeros() as usize;
100+
let end = undirected[1].trailing_zeros() as usize;
101+
let extra = 2 + weight[0][start] + weight[1][end];
102+
103+
// Heuristic
104+
let mut mask = 0;
105+
106+
for (i, edges) in undirected.iter().enumerate() {
107+
if edges.count_ones() < 4 {
108+
mask |= 1 << i;
109+
}
110+
}
111+
112+
for (i, edges) in undirected.iter_mut().enumerate() {
113+
if edges.count_ones() < 4 {
114+
*edges = (*edges & !mask) | directed[i];
115+
}
116+
}
117+
118+
Input { start, end, extra, directed, undirected, weight }
94119
}
95120

96121
pub fn part1(input: &Input) -> u32 {
97122
let mut cost = [0; 36];
98123

99124
let mut todo = VecDeque::new();
100-
todo.push_back(0);
125+
todo.push_back(input.start);
101126

102127
while let Some(from) = todo.pop_front() {
103128
let mut nodes = input.directed[from];
@@ -112,16 +137,57 @@ pub fn part1(input: &Input) -> u32 {
112137
}
113138
}
114139

115-
2 + cost[1]
140+
cost[input.end] + input.extra
116141
}
117142

118143
pub fn part2(input: &Input) -> u32 {
119-
2 + dfs(input, 0, 1, 0)
144+
let shared = AtomicU32::new(0);
145+
let threads = thread::available_parallelism().unwrap().get();
146+
147+
// Seed each worker thread with a starting state
148+
let mut seeds = VecDeque::new();
149+
seeds.push_back((input.start, 1 << input.start, 0));
150+
151+
while seeds.len() < threads {
152+
let Some((from, seen, cost)) = seeds.pop_front() else {
153+
break;
154+
};
155+
156+
if from == input.end {
157+
shared.fetch_max(cost, Ordering::Relaxed);
158+
continue;
159+
}
160+
161+
let mut nodes = input.undirected[from] & !seen;
162+
163+
while nodes > 0 {
164+
let to = nodes.trailing_zeros() as usize;
165+
let mask = 1 << to;
166+
nodes ^= mask;
167+
168+
seeds.push_back((to, seen | mask, cost + input.weight[from][to]));
169+
}
170+
}
171+
172+
// Use as many cores as possible to parallelize the remaining search.
173+
thread::scope(|scope| {
174+
for start in &seeds {
175+
scope.spawn(|| worker(input, &shared, start));
176+
}
177+
});
178+
179+
shared.load(Ordering::Relaxed) + input.extra
180+
}
181+
182+
fn worker(input: &Input, shared: &AtomicU32, start: &(usize, u64, u32)) {
183+
let (from, seen, cost) = *start;
184+
let result = dfs(input, from, seen);
185+
shared.fetch_max(result + cost, Ordering::Relaxed);
120186
}
121187

122-
fn dfs(input: &Input, from: usize, seen: u64, cost: u32) -> u32 {
123-
if from == 1 {
124-
return cost;
188+
fn dfs(input: &Input, from: usize, seen: u64) -> u32 {
189+
if from == input.end {
190+
return 0;
125191
}
126192

127193
let mut nodes = input.undirected[from] & !seen;
@@ -132,7 +198,7 @@ fn dfs(input: &Input, from: usize, seen: u64, cost: u32) -> u32 {
132198
let mask = 1 << to;
133199
nodes ^= mask;
134200

135-
result = result.max(dfs(input, to, seen | mask, cost + input.weight[from][to]));
201+
result = result.max(input.weight[from][to] + dfs(input, to, seen | mask));
136202
}
137203

138204
result

0 commit comments

Comments
 (0)