use nom::{ branch::alt, character::complete::{char, line_ending}, combinator::{map, value}, multi::{many1, separated_list1}, IResult, }; use std::{collections::BTreeSet, fs, mem}; fn main() -> Result<(), Box> { let input = fs::read_to_string("inputs/day_21.txt")?; let mut walled_garden = WalledGarden::parser(&input).unwrap().1; for _ in 0..64 { walled_garden = walled_garden.next(); } dbg!(&walled_garden.count_walkable()); let mut open_garden = OpenGarden::parser(&input).unwrap().1; // - the paths straight up / down, and left / right from the middle and edges are clear. // - this would allow finding a "single tile" version (eg part 1) to see how much of a tile could be filled at the boundaries. // - start at the top boundary I can get into, 26501365 / 131 // - when I find a fully explored garden, I can extrapolate that any closer gardens are also filled out // - for filled out gardens, there will be an 'even' and an 'odd' answer (alternating checkerboard). choose the right one to add it. // - this is still around 202300 pages in each direction, so multiplying the rows etc seems like a good idea. Eg do the two sides, then multiply out the fully explored middle. let open_iters = 26501365; for i in 0..open_iters { if i % 100000 == 0 { println!("{}", i as f32 / open_iters as f32); } open_garden.next_mut(); } dbg!(&open_garden.count_walkable()); Ok(()) } #[derive(Debug)] struct WalledGarden(Vec>); #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum WalledGardenState { Empty, Walkable, Rock, } impl WalledGarden { fn parser(input: &str) -> IResult<&str, Self> { map( separated_list1(line_ending, many1(WalledGardenState::parser)), WalledGarden, )(input) } fn next(&self) -> WalledGarden { WalledGarden( (0..self.0.len() as isize) .map(|y| { (0..self.0[y as usize].len() as isize) .map(|x| { let current = self.get(y, x); let neighbours = [ self.get(y - 1, x), self.get(y + 1, x), self.get(y, x - 1), self.get(y, x + 1), ]; if current == WalledGardenState::Rock { WalledGardenState::Rock } else if neighbours.contains(&WalledGardenState::Walkable) { WalledGardenState::Walkable } else { WalledGardenState::Empty } }) .collect::>() }) .collect(), ) } fn get(&self, y: isize, x: isize) -> WalledGardenState { if y < 0 || x < 0 { WalledGardenState::Rock } else { let y = y as usize; let x = x as usize; if y >= self.0.len() || x >= self.0[y].len() { WalledGardenState::Rock } else { self.0[y][x] } } } fn count_walkable(&self) -> usize { self.0 .iter() .flat_map(|row| row.iter()) .filter(|s| **s == WalledGardenState::Walkable) .count() } } impl WalledGardenState { fn parser(input: &str) -> IResult<&str, Self> { alt(( value(WalledGardenState::Empty, char('.')), value(WalledGardenState::Walkable, char('S')), value(WalledGardenState::Rock, char('#')), ))(input) } } #[derive(Debug)] struct OpenGarden { rockmap_size: (isize, isize), rocks: BTreeSet<(isize, isize)>, walkable: BTreeSet<(isize, isize)>, } impl OpenGarden { fn parser(input: &str) -> IResult<&str, Self> { map( separated_list1(line_ending, many1(WalledGardenState::parser)), |walled_garden_map| OpenGarden { rockmap_size: ( walled_garden_map.len() as isize, walled_garden_map[0].len() as isize, ), rocks: walled_garden_map .iter() .enumerate() .flat_map(|(y, row)| { row.iter().enumerate().filter_map(move |(x, s)| { (*s == WalledGardenState::Rock).then(|| (y as isize, x as isize)) }) }) .collect(), walkable: walled_garden_map .iter() .enumerate() .flat_map(|(y, row)| { row.iter().enumerate().filter_map(move |(x, s)| { (*s == WalledGardenState::Walkable).then(|| (y as isize, x as isize)) }) }) .collect(), }, )(input) } fn next_mut(&mut self) { let walkable = mem::take(&mut self.walkable); self.walkable = walkable .iter() .flat_map(|(y, x)| [(y - 1, *x), (y + 1, *x), (*y, x - 1), (*y, x + 1)]) .filter(|(y, x)| !self.is_rock(*y, *x)) .collect(); } fn is_rock(&self, y: isize, x: isize) -> bool { let y = y.rem_euclid(self.rockmap_size.0); let x = x.rem_euclid(self.rockmap_size.1); self.rocks.contains(&(y, x)) } fn count_walkable(&self) -> usize { self.walkable.len() } }