312 lines
8.3 KiB
Rust
312 lines
8.3 KiB
Rust
use std::fs::File;
|
|
use std::io::{BufRead, BufReader, Lines};
|
|
|
|
// BOILERPLATE
|
|
type InputIter = Lines<BufReader<File>>;
|
|
|
|
fn get_input() -> InputIter {
|
|
let f = File::open("input").unwrap();
|
|
let br = BufReader::new(f);
|
|
br.lines()
|
|
}
|
|
|
|
fn main() {
|
|
println!("Problem 1 solution: {}", problem1(get_input()));
|
|
println!("Problem 2 solution: {}", problem2(get_input()));
|
|
}
|
|
|
|
// PARSE
|
|
|
|
#[derive(Debug)]
|
|
struct MapCell {
|
|
dist: u64,
|
|
kind: char,
|
|
inside: bool,
|
|
}
|
|
|
|
impl From<char> for MapCell {
|
|
fn from(c: char) -> Self {
|
|
MapCell {
|
|
dist: 0,
|
|
kind: c,
|
|
inside: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct PipeMap {
|
|
map: Vec<Vec<MapCell>>,
|
|
start: (usize, usize),
|
|
}
|
|
|
|
const ALL_ADJ: [(isize, isize); 8] = [
|
|
(-1, -1),
|
|
(0, -1),
|
|
(1, -1),
|
|
(-1, 0),
|
|
(1, 0),
|
|
(-1, 1),
|
|
(0, 1),
|
|
(1, 1),
|
|
];
|
|
|
|
impl PipeMap {
|
|
fn adjacencies(c: char) -> Option<[(isize, isize); 2]> {
|
|
match c {
|
|
'|' => Some([(0, -1), (0, 1)]),
|
|
'-' => Some([(-1, 0), (1, 0)]),
|
|
'L' => Some([(0, -1), (1, 0)]),
|
|
'J' => Some([(0, -1), (-1, 0)]),
|
|
'7' => Some([(0, 1), (-1, 0)]),
|
|
'F' => Some([(0, 1), (1, 0)]),
|
|
'.' => None,
|
|
'S' => None,
|
|
_ => panic!("unhandled type"),
|
|
}
|
|
}
|
|
fn valid_pos(&self, pos: (isize, isize)) -> bool {
|
|
pos.0 >= 0
|
|
&& pos.0 < self.map[0].len() as isize
|
|
&& pos.1 >= 0
|
|
&& pos.1 < self.map.len() as isize
|
|
}
|
|
|
|
fn apply_adj(&self, pos: (usize, usize), adj: (isize, isize)) -> Option<(usize, usize)> {
|
|
if self.valid_pos((pos.0 as isize + adj.0, pos.1 as isize + adj.1)) {
|
|
Some((
|
|
(pos.0 as isize + adj.0) as usize,
|
|
(pos.1 as isize + adj.1) as usize,
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn adjacent_positions(&self, pos: (usize, usize)) -> Option<[(usize, usize); 2]> {
|
|
let adj = Self::adjacencies(self.map[pos.1][pos.0].kind);
|
|
if let Some(adj) = adj {
|
|
let mut positions = adj.iter().filter_map(|adj| self.apply_adj(pos, *adj));
|
|
Some([positions.next().unwrap(), positions.next().unwrap()])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn start_adjacencies(&self) -> [(usize, usize); 2] {
|
|
let mut adj_positions = Vec::new();
|
|
for neighbour in ALL_ADJ
|
|
.iter()
|
|
.filter_map(|adj| self.apply_adj(self.start, *adj))
|
|
{
|
|
if let Some(neigh_adjs) = self.adjacent_positions(neighbour) {
|
|
if neigh_adjs.contains(&self.start) {
|
|
adj_positions.push(neighbour)
|
|
}
|
|
}
|
|
}
|
|
|
|
[adj_positions[0], adj_positions[1]]
|
|
}
|
|
|
|
fn mark_inside(&mut self) {
|
|
let start_adj = self.start_adjacencies();
|
|
|
|
let start_kind = if start_adj.contains(
|
|
&self
|
|
.apply_adj(self.start, (0, -1))
|
|
.unwrap_or((99999, 99999)),
|
|
) && start_adj
|
|
.contains(&self.apply_adj(self.start, (0, 1)).unwrap_or((99999, 99999)))
|
|
{
|
|
'|'
|
|
} else if start_adj.contains(
|
|
&self
|
|
.apply_adj(self.start, (-1, 0))
|
|
.unwrap_or((99999, 99999)),
|
|
) && start_adj
|
|
.contains(&self.apply_adj(self.start, (1, 0)).unwrap_or((99999, 99999)))
|
|
{
|
|
'-'
|
|
} else if start_adj.contains(
|
|
&self
|
|
.apply_adj(self.start, (0, -1))
|
|
.unwrap_or((99999, 99999)),
|
|
) && start_adj
|
|
.contains(&self.apply_adj(self.start, (1, 0)).unwrap_or((99999, 99999)))
|
|
{
|
|
'L'
|
|
} else if start_adj.contains(
|
|
&self
|
|
.apply_adj(self.start, (0, -1))
|
|
.unwrap_or((99999, 99999)),
|
|
) && start_adj.contains(
|
|
&self
|
|
.apply_adj(self.start, (-1, 0))
|
|
.unwrap_or((99999, 99999)),
|
|
) {
|
|
'J'
|
|
} else if start_adj.contains(&self.apply_adj(self.start, (0, 1)).unwrap_or((99999, 99999)))
|
|
&& start_adj.contains(
|
|
&self
|
|
.apply_adj(self.start, (-1, 0))
|
|
.unwrap_or((99999, 99999)),
|
|
)
|
|
{
|
|
'7'
|
|
} else if start_adj.contains(&self.apply_adj(self.start, (0, 1)).unwrap_or((99999, 99999)))
|
|
&& start_adj.contains(&self.apply_adj(self.start, (1, 0)).unwrap_or((99999, 99999)))
|
|
{
|
|
'F'
|
|
} else {
|
|
panic!("invalid start");
|
|
};
|
|
|
|
for row in &mut self.map {
|
|
let mut inside = false;
|
|
for cell in row {
|
|
let mut kind = cell.kind;
|
|
if cell.dist == 0 && kind != 'S' {
|
|
cell.inside = inside;
|
|
} else {
|
|
if kind == 'S' {
|
|
kind = start_kind;
|
|
}
|
|
if kind == '|' || kind == 'L' || kind == 'J' {
|
|
inside = !inside;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: BufRead> From<Lines<T>> for PipeMap {
|
|
fn from(lines: Lines<T>) -> Self {
|
|
let mut map: Vec<Vec<MapCell>> = Vec::new();
|
|
for line in lines {
|
|
map.push(line.unwrap().chars().map(MapCell::from).collect());
|
|
}
|
|
|
|
let start = map
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(idx, row)| (row.iter().position(|p| p.kind == 'S'), idx))
|
|
.find(|(x, _)| x.is_some())
|
|
.unwrap();
|
|
let start = (start.0.unwrap(), start.1);
|
|
|
|
let mut pipemap = PipeMap { map, start };
|
|
|
|
let mut cur_positions = pipemap.start_adjacencies();
|
|
let mut cur_distance = 1;
|
|
loop {
|
|
for pos in cur_positions {
|
|
pipemap.map[pos.1][pos.0].dist = cur_distance;
|
|
}
|
|
cur_distance += 1;
|
|
|
|
if let Some(new_pos1) = pipemap.adjacent_positions(cur_positions[0]) {
|
|
if let Some(new_pos1) = new_pos1.iter().find(|pos| {
|
|
pipemap.map[pos.1][pos.0].dist == 0 && pipemap.map[pos.1][pos.0].kind != 'S'
|
|
}) {
|
|
cur_positions[0] = *new_pos1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if let Some(new_pos2) = pipemap.adjacent_positions(cur_positions[1]) {
|
|
if let Some(new_pos2) = new_pos2.iter().find(|pos| {
|
|
pipemap.map[pos.1][pos.0].dist == 0 && pipemap.map[pos.1][pos.0].kind != 'S'
|
|
}) {
|
|
cur_positions[1] = *new_pos2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if cur_positions[0] == cur_positions[1] {
|
|
pipemap.map[cur_positions[0].1][cur_positions[0].0].dist = cur_distance;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pipemap
|
|
}
|
|
}
|
|
|
|
// PROBLEM 1 solution
|
|
|
|
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
|
let map = PipeMap::from(input);
|
|
|
|
map.map
|
|
.iter()
|
|
.map(|row| row.iter().map(|cell| cell.dist).max().unwrap())
|
|
.max()
|
|
.unwrap()
|
|
}
|
|
|
|
// PROBLEM 2 solution
|
|
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
|
let mut map = PipeMap::from(input);
|
|
map.mark_inside();
|
|
|
|
for row in &map.map {
|
|
for cell in row {
|
|
print!("{}", cell.kind);
|
|
}
|
|
print!(" ");
|
|
for cell in row {
|
|
print!(
|
|
"{}",
|
|
match cell.inside {
|
|
true => 'I',
|
|
false => 'O',
|
|
}
|
|
);
|
|
}
|
|
println!();
|
|
}
|
|
|
|
map.map
|
|
.iter()
|
|
.map(|row| row.iter().filter(|cell| cell.inside).count())
|
|
.sum::<usize>() as u64
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::*;
|
|
use std::io::Cursor;
|
|
|
|
const EXAMPLE: &str = &"..F7.
|
|
.FJ|.
|
|
SJ.L7
|
|
|F--J
|
|
LJ...";
|
|
|
|
const EXAMPLE2: &str = &"FF7FSF7F7F7F7F7F---7
|
|
L|LJ||||||||||||F--J
|
|
FL-7LJLJ||||||LJL-77
|
|
F--JF--7||LJLJ7F7FJ-
|
|
L---JF-JLJ.||-FJLJJ7
|
|
|F|F-JF---7F7-L7L|7|
|
|
|FFJF7L7F-JF7|JL---7
|
|
7-L-JL7||F7|L7F-7F7|
|
|
L.L7LFJ|||||FJL7||LJ
|
|
L7JLJL-JLJLJL--JLJ.L";
|
|
|
|
#[test]
|
|
fn problem1_example() {
|
|
let c = Cursor::new(EXAMPLE);
|
|
assert_eq!(problem1(c.lines()), 8);
|
|
}
|
|
|
|
#[test]
|
|
fn problem2_example() {
|
|
let c = Cursor::new(EXAMPLE2);
|
|
assert_eq!(problem2(c.lines()), 10);
|
|
}
|
|
}
|