Improve day12 performance, add tim's solution
This commit is contained in:
parent
0e87cb94d3
commit
59949a63ec
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -2,12 +2,29 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrays"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dbdbb2877d26e0647c6b8125802b2ecf2dc2a28d864dde41e6f9b9a54da08fe"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "beef"
|
||||
version = "0.5.2"
|
||||
|
@ -158,9 +175,15 @@ name = "day12"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colorous",
|
||||
"hashbrown 0.13.1",
|
||||
"priority-queue",
|
||||
"termion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "day12-tim"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "day13"
|
||||
version = "0.1.0"
|
||||
|
@ -247,6 +270,21 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
|
@ -262,6 +300,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.3"
|
||||
|
@ -321,9 +369,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
version = "0.2.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
|
@ -378,6 +426,16 @@ version = "0.2.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "priority-queue"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7685ca4cc0b3ad748c22ce6803e23b55b9206ef7715b965ebeaf41639238fdc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
|
@ -476,9 +534,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.4"
|
||||
version = "0.36.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
|
||||
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = [ "day01", "day01-gen-problem", "day02", "day02-gen-problem", "day03", "day03-gen-problem", "day04", "day05", "day06", "day07", "day08", "day09", "day10", "day11", "day12", "day13", "day14", "day15", "day16", "day17", "day18", "day19", "day20", "day21", "day22", "day23", "day24", ]
|
||||
members = [ "day01", "day01-gen-problem", "day02", "day02-gen-problem", "day03", "day03-gen-problem", "day04", "day05", "day06", "day07", "day08", "day09", "day10", "day11", "day12", "day12-tim", "day13", "day14", "day15", "day16", "day17", "day18", "day19", "day20", "day21", "day22", "day23", "day24", ]
|
||||
|
||||
[profile.release]
|
||||
#debug = true
|
||||
|
|
8
day12-tim/Cargo.toml
Normal file
8
day12-tim/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "day12-tim"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
128
day12-tim/src/main.rs
Normal file
128
day12-tim/src/main.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, BufRead, Error}
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Position {
|
||||
row: usize,
|
||||
col: usize
|
||||
}
|
||||
fn main() -> Result<(), Error> {
|
||||
// You can pass the input filename as argument or otherwise
|
||||
// it will use "input01.txt".
|
||||
let file_name = ::std::env::args()
|
||||
.skip(1)
|
||||
.next()
|
||||
.unwrap_or_else(|| String::from("./src/input01.txt"));
|
||||
println!("Reading in '{}' and solving task 1 and 2.", file_name);
|
||||
let reader = BufReader::new(File::open(file_name).expect("Input file not found!"));
|
||||
|
||||
// Import the data and convert every character to an integer representation.
|
||||
// a = 0, ... , z = 25.
|
||||
const HEIGHT_OF_START_POSITION: i32 = 0;
|
||||
const HEIGHT_OF_END_POSITION: i32 = 25;
|
||||
const MAX_HEIGHT_WE_CAN_CLIMB: i32 = 1;
|
||||
let mut start_position = Position {row: 0, col: 0};
|
||||
let mut end_position = Position {row: 0, col: 0};
|
||||
let data: Vec<Vec<i32>> = reader
|
||||
.lines().enumerate()
|
||||
.map(|(row, line)| line.unwrap().chars().enumerate()
|
||||
.map(|(col, character)| {
|
||||
if character == 'S' {
|
||||
start_position.row = row;
|
||||
start_position.col = col;
|
||||
return HEIGHT_OF_START_POSITION;
|
||||
}
|
||||
else if character == 'E' {
|
||||
end_position.row = row;
|
||||
end_position.col = col;
|
||||
return HEIGHT_OF_END_POSITION;
|
||||
}
|
||||
else {
|
||||
return character as i32 - 'a' as i32;
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
.collect();
|
||||
|
||||
// Get some informations about the input data.
|
||||
let number_of_rows = data.len();
|
||||
let number_of_cols = data[0].len();
|
||||
|
||||
// Dijkstra.
|
||||
// Originally i used an if statement to determine if we are solving part 1 or 2.
|
||||
// Based on the task i set either the starting or the end point as initial position.
|
||||
// But time measurement showed that backwards is much faster.
|
||||
// So we will always start from the end.
|
||||
let mut needed_steps: Vec<Vec<u32>> =
|
||||
vec![vec![std::u32::MAX; number_of_cols]; number_of_rows];
|
||||
let mut visited_positions: Vec<Vec<bool>> =
|
||||
vec![vec![false; number_of_cols]; number_of_rows];
|
||||
let mut unvisited_neighbours = Vec::<Position>::new();
|
||||
needed_steps[end_position.row][end_position.col] = 0;
|
||||
visited_positions[end_position.row][end_position.col] = true;
|
||||
unvisited_neighbours.push(end_position);
|
||||
while unvisited_neighbours.len() > 0 {
|
||||
// Get the position to use based on the lowest needed steps.
|
||||
let index_current_position: Option<usize> = unvisited_neighbours
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, pos1), (_, pos2)|
|
||||
needed_steps[pos1.row][pos1.col].cmp(&needed_steps[pos2.row][pos2.col]))
|
||||
.map(|(index, _)| index);
|
||||
let index_current_position = index_current_position.unwrap();
|
||||
let current_row = unvisited_neighbours[index_current_position].row;
|
||||
let current_col = unvisited_neighbours[index_current_position].col;
|
||||
// Check what neighbours are reachable and if we can reach them faster than before.
|
||||
// Therefore check the size of the map and also if we can reach the
|
||||
// next step based on the height.
|
||||
for look_vector in vec![vec![1,0],vec![0,1],vec![-1,0],vec![0,-1]] {
|
||||
let look_vertical = look_vector[0];
|
||||
let look_horizontal = look_vector[1];
|
||||
let row = unvisited_neighbours[index_current_position].row as i32 + look_vertical;
|
||||
let col = unvisited_neighbours[index_current_position].col as i32 + look_horizontal;
|
||||
// Check size of the map.
|
||||
// QUESTION: Can we take use of the auto bound check of rust?
|
||||
if (row < 0) || (col < 0) || (row >= number_of_rows as i32) || (col >= number_of_cols as i32) {
|
||||
continue;
|
||||
}
|
||||
let row = row as usize;
|
||||
let col = col as usize;
|
||||
// Check if we can reach it without climbing. We go backwards.
|
||||
if data[current_row][current_col] <= data[row][col] + MAX_HEIGHT_WE_CAN_CLIMB {
|
||||
// We can go there. Check if this is faster than before.
|
||||
if needed_steps[row][col] > needed_steps[current_row][current_col] + 1 {
|
||||
needed_steps[row][col] = needed_steps[current_row][current_col] + 1;
|
||||
}
|
||||
// If we have not been on this position before, put it on the list.
|
||||
if visited_positions[row][col] == false {
|
||||
unvisited_neighbours.push(Position{row: row, col: col});
|
||||
visited_positions[row][col] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We have now looked at all neighbours. Delete the current position from the vector
|
||||
// and mark it as visited.
|
||||
unvisited_neighbours.remove(index_current_position);
|
||||
}
|
||||
|
||||
// We got the 2d vector with all the distances from the end position.
|
||||
// For task 1 we only need to look at the start position.
|
||||
println!("Solution for task 1: {}", needed_steps[start_position.row][start_position.col]);
|
||||
|
||||
// For task 2 we need to find the nearest position at height 0.
|
||||
let result_task_2 = needed_steps
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(row, row_values)| row_values.iter().enumerate()
|
||||
.filter(|&(col, _)| data[row][col] == 0)
|
||||
.min_by_key(|&(_, value)| value))
|
||||
.min_by_key(|&(_, value)| value)
|
||||
.map(|(_, value)| value)
|
||||
.unwrap();
|
||||
println!("Solution for task 2: {}", result_task_2);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -9,4 +9,6 @@ app = true
|
|||
|
||||
[dependencies]
|
||||
colorous = "1.0.8"
|
||||
hashbrown = "0.13.1"
|
||||
priority-queue = "1.3.0"
|
||||
termion = "2.0.1"
|
||||
|
|
130
day12/src/height_map.rs
Normal file
130
day12/src/height_map.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use std::{cmp::Reverse, io::Read};
|
||||
|
||||
use hashbrown::hash_map::DefaultHashBuilder;
|
||||
use priority_queue::PriorityQueue;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HeightMap {
|
||||
heights: Vec<u8>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl HeightMap {
|
||||
pub fn find_shortest_path_len_to_end<const TARGET: u8>(&self) -> usize {
|
||||
let mut iterations = 0;
|
||||
let mut visited: Vec<bool> = self.heights.iter().map(|_| false).collect();
|
||||
let mut open: PriorityQueue<usize, Reverse<u32>, DefaultHashBuilder> =
|
||||
PriorityQueue::with_hasher(DefaultHashBuilder::default());
|
||||
let end_idx = self
|
||||
.heights
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, &height)| height == b'E')
|
||||
.unwrap()
|
||||
.0;
|
||||
open.push(end_idx, Reverse(0));
|
||||
while let Some((current_idx, Reverse(current_score))) = open.pop() {
|
||||
iterations += 1;
|
||||
if self.heights[current_idx] == TARGET {
|
||||
eprintln!(
|
||||
"Iter: {iterations} | {}x{} = {}",
|
||||
self.width,
|
||||
self.height,
|
||||
self.height * self.width
|
||||
);
|
||||
return current_score as usize;
|
||||
}
|
||||
visited[current_idx] = true;
|
||||
for neighbor_idx in self.neighbors_of(current_idx) {
|
||||
let tentative_score = current_score + 1;
|
||||
if !visited[neighbor_idx] {
|
||||
open.push(neighbor_idx, Reverse(tentative_score));
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("Did not reach the target");
|
||||
}
|
||||
|
||||
fn neighbors_of(&self, current: usize) -> impl Iterator<Item = usize> + '_ {
|
||||
let (x, y) = self.coords_of(current);
|
||||
let curr_height = self.height_of(current);
|
||||
[
|
||||
(x.saturating_sub(1), y),
|
||||
(x, y.saturating_sub(1)),
|
||||
(x + 1, y),
|
||||
(x, y + 1),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(move |&(x, _)| x < self.width)
|
||||
.filter(|&(_, y)| y < self.height)
|
||||
.filter(move |&(new_x, new_y)| new_x != x || new_y != y)
|
||||
.map(|neigh| self.index_of(&neigh))
|
||||
.filter(move |&idx| self.height_of(idx) >= curr_height - 1)
|
||||
}
|
||||
|
||||
fn coords_of(&self, current: usize) -> (usize, usize) {
|
||||
(current % self.width, current / self.width)
|
||||
}
|
||||
|
||||
fn index_of(&self, (x, y): &(usize, usize)) -> usize {
|
||||
x + y * self.width
|
||||
}
|
||||
|
||||
fn height_of(&self, idx: usize) -> u8 {
|
||||
match self.heights[idx] {
|
||||
b'S' => b'a',
|
||||
b'E' => b'z',
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_fancy(&self) {
|
||||
let sup = (b'z' - b'a' + 1) as usize;
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
let idx = self.index_of(&(x, y));
|
||||
let height = self.height_of(idx) - b'a';
|
||||
let char = if self.heights[idx] == b'S' {
|
||||
'S'
|
||||
} else if self.heights[idx] == b'E' {
|
||||
'E'
|
||||
} else {
|
||||
'█'
|
||||
};
|
||||
let color = colorous::RED_YELLOW_GREEN.eval_rational(26 - height as usize, sup);
|
||||
print!(
|
||||
"{}{}{char}",
|
||||
termion::color::Fg(termion::color::Rgb(color.r, color.g, color.b)),
|
||||
termion::color::Bg(termion::color::Rgb(0, 0, 0))
|
||||
);
|
||||
}
|
||||
println!()
|
||||
}
|
||||
println!()
|
||||
}
|
||||
}
|
||||
impl<R: Read> From<R> for HeightMap {
|
||||
fn from(reader: R) -> Self {
|
||||
let mut width = None;
|
||||
let heights: Vec<_> = reader
|
||||
.bytes()
|
||||
.map(Result::unwrap)
|
||||
.enumerate()
|
||||
.inspect(|&(idx, byte)| {
|
||||
if width.is_none() && byte == b'\n' {
|
||||
width = Some(idx);
|
||||
}
|
||||
})
|
||||
.map(|(_, byte)| byte)
|
||||
.filter(|&byte| byte != b'\n')
|
||||
.collect();
|
||||
let width = width.unwrap_or(heights.len());
|
||||
let height = heights.len() / width;
|
||||
HeightMap {
|
||||
heights,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
use std::{
|
||||
cmp::Reverse,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Read},
|
||||
io::{BufRead, BufReader},
|
||||
};
|
||||
|
||||
use height_map::HeightMap;
|
||||
|
||||
mod height_map;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
enum SolvePuzzle {
|
||||
First,
|
||||
|
@ -12,134 +15,9 @@ enum SolvePuzzle {
|
|||
Print,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HeightMap {
|
||||
heights: Vec<u8>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Ord)]
|
||||
struct ScoredPos {
|
||||
height: u8,
|
||||
score: u32,
|
||||
index_on_map: usize,
|
||||
}
|
||||
|
||||
impl HeightMap {
|
||||
pub fn solve_a_star<const STARTING: u8>(&self) -> usize {
|
||||
let mut entries: Vec<_> = self
|
||||
.heights
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, &height)| ScoredPos {
|
||||
height,
|
||||
score: if height == STARTING { 0 } else { u32::MAX },
|
||||
index_on_map: idx,
|
||||
})
|
||||
.collect();
|
||||
let mut open: Vec<usize> = entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, entry)| entry.height == STARTING)
|
||||
.map(|(idx, _)| idx)
|
||||
.collect();
|
||||
open.sort_by_cached_key(|&idx| Reverse(entries[idx].score));
|
||||
let mut came_from: Vec<Option<usize>> =
|
||||
::std::iter::repeat(None).take(self.heights.len()).collect();
|
||||
while !open.is_empty() {
|
||||
let current_idx = *open.last().unwrap();
|
||||
if entries[current_idx].height == b'E' {
|
||||
return self.count_path(&came_from, current_idx);
|
||||
}
|
||||
let current_idx = open.pop().unwrap();
|
||||
for neighbor_idx in self.neighbors_of(current_idx) {
|
||||
let tentative_score = &entries[current_idx].score + 1;
|
||||
let neighbor = &mut entries[neighbor_idx];
|
||||
if tentative_score < neighbor.score {
|
||||
came_from[neighbor_idx] = Some(current_idx);
|
||||
neighbor.score = tentative_score;
|
||||
if !open.contains(&neighbor_idx) {
|
||||
open.push(neighbor_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
open.sort_by_cached_key(|&idx| Reverse(entries[idx].score));
|
||||
}
|
||||
panic!()
|
||||
}
|
||||
|
||||
fn neighbors_of(&self, current: usize) -> impl Iterator<Item = usize> + '_ {
|
||||
let (x, y) = self.coords_of(current);
|
||||
let curr_height = self.height_of(current);
|
||||
[
|
||||
(x.saturating_sub(1), y),
|
||||
(x, y.saturating_sub(1)),
|
||||
(x + 1, y),
|
||||
(x, y + 1),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(move |&(x, _)| x < self.width)
|
||||
.filter(|&(_, y)| y < self.height)
|
||||
.filter(move |&(new_x, new_y)| new_x != x || new_y != y)
|
||||
.map(|neigh| self.index_of(&neigh))
|
||||
.filter(move |&idx| self.height_of(idx) <= curr_height + 1)
|
||||
}
|
||||
|
||||
fn coords_of(&self, current: usize) -> (usize, usize) {
|
||||
(current % self.width, current / self.width)
|
||||
}
|
||||
|
||||
fn index_of(&self, (x, y): &(usize, usize)) -> usize {
|
||||
x + y * self.width
|
||||
}
|
||||
|
||||
fn height_of(&self, idx: usize) -> u8 {
|
||||
match self.heights[idx] {
|
||||
b'S' => b'a',
|
||||
b'E' => b'z',
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn count_path(&self, came_from: &[Option<usize>], mut current: usize) -> usize {
|
||||
let mut count = 0;
|
||||
while let Some(new) = came_from[current] {
|
||||
count += 1;
|
||||
current = new;
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
fn print_fancy(&self) {
|
||||
let sup = (b'z' - b'a' + 1) as usize;
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
let idx = self.index_of(&(x, y));
|
||||
let height = self.height_of(idx) - b'a';
|
||||
let char = if self.heights[idx] == b'S' {
|
||||
'S'
|
||||
} else if self.heights[idx] == b'E' {
|
||||
'E'
|
||||
} else {
|
||||
'█'
|
||||
};
|
||||
let color = colorous::RED_YELLOW_GREEN.eval_rational(26 - height as usize, sup);
|
||||
print!(
|
||||
"{}{}{char}",
|
||||
termion::color::Fg(termion::color::Rgb(color.r, color.g, color.b)),
|
||||
termion::color::Bg(termion::color::Rgb(0, 0, 0))
|
||||
);
|
||||
}
|
||||
println!()
|
||||
}
|
||||
println!()
|
||||
}
|
||||
}
|
||||
|
||||
fn solve_puzzle<const STARTING: u8, R: BufRead>(reader: R) -> usize {
|
||||
let map = HeightMap::from(reader);
|
||||
map.solve_a_star::<STARTING>()
|
||||
map.find_shortest_path_len_to_end::<STARTING>()
|
||||
}
|
||||
|
||||
fn print_puzzle<R: BufRead>(reader: R) -> usize {
|
||||
|
@ -169,45 +47,21 @@ fn main() {
|
|||
println!("{}", solution);
|
||||
}
|
||||
|
||||
impl<R: Read> From<R> for HeightMap {
|
||||
fn from(reader: R) -> Self {
|
||||
let mut width = None;
|
||||
let heights: Vec<_> = reader
|
||||
.bytes()
|
||||
.map(Result::unwrap)
|
||||
.enumerate()
|
||||
.inspect(|&(idx, byte)| {
|
||||
if width.is_none() && byte == b'\n' {
|
||||
width = Some(idx);
|
||||
}
|
||||
})
|
||||
.map(|(_, byte)| byte)
|
||||
.filter(|&byte| byte != b'\n')
|
||||
.collect();
|
||||
let width = width.unwrap_or(heights.len());
|
||||
let height = heights.len() / width;
|
||||
HeightMap {
|
||||
heights,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
impl PartialEq for ScoredPos {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.height == other.height
|
||||
}
|
||||
}
|
||||
use super::*;
|
||||
|
||||
impl PartialOrd for ScoredPos {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
if self.height < other.height {
|
||||
Some(::std::cmp::Ordering::Less)
|
||||
} else if self.height < other.height {
|
||||
Some(::std::cmp::Ordering::Greater)
|
||||
} else {
|
||||
Some(::std::cmp::Ordering::Equal)
|
||||
}
|
||||
#[test]
|
||||
fn example_on_first() {
|
||||
let reader = Cursor::new(include_str!("../input.test"));
|
||||
assert_eq!(solve_puzzle::<b'S', _>(reader), 31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_on_second() {
|
||||
let reader = Cursor::new(include_str!("../input.test"));
|
||||
assert_eq!(solve_puzzle::<b'a', _>(reader), 29);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue