advent_of_code/year2016/
day24.rs1use crate::common::permutation::all_permutations;
2use crate::input::Input;
3use std::cmp::Reverse;
4use std::collections::{BinaryHeap, HashMap, HashSet};
5
6struct Grid {
7 cols: usize,
8 data: Vec<bool>,
9 locations: Vec<(usize, usize)>,
10}
11
12impl Grid {
13 fn parse(input: &str) -> Result<Self, String> {
14 let rows = input.lines().count();
15 let cols = input.lines().next().ok_or("Empty input")?.len();
16 let mut locations = Vec::new();
17 let mut data = vec![true; rows * cols];
18
19 for (y, line) in input.lines().enumerate() {
20 if line.len() != cols {
21 return Err("Not all rows have equal length".into());
22 }
23 for (x, &c) in line.as_bytes().iter().enumerate() {
24 data[y * cols + x] = match c {
25 b'#' => false,
26 b'.' => true,
27 b'0'..=b'7' => {
28 if x == 0 || y == 0 || x + 1 == cols || y + 1 == rows {
29 return Err("Number at edge".into());
30 }
31 let number = (c - b'0') as usize;
32 if number >= locations.len() {
33 locations.resize(number + 1, (0, 0));
34 }
35 locations[number] = (x, y);
36 true
37 }
38 _ => {
39 return Err(format!("Invalid char in input: '{}'", c as char));
40 }
41 };
42 }
43 }
44
45 Ok(Self {
46 cols,
47 data,
48 locations,
49 })
50 }
51
52 fn is_open(&self, location: (usize, usize)) -> bool {
53 self.data[location.1 * self.cols + location.0]
54 }
55}
56
57pub fn solve(input: &Input) -> Result<usize, String> {
58 let grid = Grid::parse(input.text)?;
59 let mut distances: HashMap<(usize, usize), usize> = HashMap::new();
60
61 for from in 0..grid.locations.len() {
62 'toloop: for to in 0..grid.locations.len() {
63 if to <= from {
64 continue;
65 }
66
67 let starting_location = grid.locations[from];
68 let target_location = grid.locations[to];
69 if starting_location == (0, 0) || target_location == (0, 0) {
70 return Err("Not all digits in grid".into());
71 }
72
73 let mut visited = HashSet::new();
74 let mut to_visit = BinaryHeap::new();
75 to_visit.push(Reverse((0, 0, starting_location)));
76
77 while let Some(Reverse((_heuristic, distance, location))) = to_visit.pop() {
78 if location == target_location {
79 distances.insert((from, to), distance);
80 continue 'toloop;
81 }
82
83 for diff in [(1_i32, 0_i32), (-1, 0), (0, 1), (0, -1)] {
84 if diff.0 == -1 && location.0 == 0 || diff.1 == -1 && location.1 == 0 {
85 continue;
86 }
87 let new_location = (
88 (location.0 as i32 + diff.0) as usize,
89 (location.1 as i32 + diff.1) as usize,
90 );
91 if grid.is_open(new_location) && visited.insert(new_location) {
92 let new_distance = distance + 1;
93 let heuristic = (new_location.0 as i32 - target_location.0 as i32)
94 .unsigned_abs() as usize
95 + (new_location.1 as i32 - target_location.1 as i32).unsigned_abs()
96 as usize;
97 to_visit.push(Reverse((
98 new_distance + heuristic,
99 new_distance,
100 new_location,
101 )));
102 }
103 }
104 }
105 }
106 }
107
108 let mut initial_order = (1..grid.locations.len()).collect::<Vec<usize>>();
109 let mut answer = usize::MAX;
110 all_permutations(
111 &mut initial_order,
112 &mut |order: &[usize]| -> Result<(), String> {
113 let mut current_location = 0_usize;
114 let mut total_distance = 0;
115 for &n in order.iter() {
116 let key = (
117 std::cmp::min(current_location, n),
118 std::cmp::max(current_location, n),
119 );
120 total_distance += distances.get(&key).unwrap_or(&0);
121 current_location = n;
122 }
123 if input.is_part_two() {
124 total_distance += distances.get(&(0, current_location)).unwrap_or(&0);
125 }
126 answer = std::cmp::min(answer, total_distance);
127 Ok(())
128 },
129 )?;
130
131 Ok(answer)
132}
133
134#[test]
135pub fn tests() {
136 use crate::input::{test_part_one, test_part_two};
137
138 test_part_one!("###########\n#0.1.....2#\n#.#######.#\n#4.......3#\n###########" => 14);
139
140 let real_input = include_str!("day24_input.txt");
141 test_part_one!(real_input => 412);
142 test_part_two!(real_input => 664);
143}