aoc2023/10/src/main.rs
Keenan Tims ebd1d0ff94
day10: problem 2 solution
slightly cheated for ideas on part 2. still a pretty ugly solution.
2023-12-10 00:02:29 -08:00

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);
}
}