diff --git a/23/Cargo.lock b/23/Cargo.lock new file mode 100644 index 0000000..e856710 --- /dev/null +++ b/23/Cargo.lock @@ -0,0 +1,128 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "day23" +version = "0.1.0" +dependencies = [ + "itertools", + "ndarray", + "petgraph", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" diff --git a/23/Cargo.toml b/23/Cargo.toml new file mode 100644 index 0000000..177fd7a --- /dev/null +++ b/23/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "day23" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +itertools = "0.12.0" +ndarray = "0.15.6" +petgraph = "0.6.4" diff --git a/23/src/main.rs b/23/src/main.rs new file mode 100644 index 0000000..9b1a89d --- /dev/null +++ b/23/src/main.rs @@ -0,0 +1,236 @@ +use itertools::Itertools; +use ndarray::prelude::*; +use petgraph::algo::all_simple_paths; +use petgraph::prelude::*; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Write}; +use std::fs::File; +use std::io::{BufRead, BufReader, Lines}; +use std::time::Instant; + +// BOILERPLATE +type InputIter = Lines>; + +fn get_input() -> InputIter { + let f = File::open("input").unwrap(); + let br = BufReader::new(f); + br.lines() +} + +fn main() { + let start = Instant::now(); + let ans1 = problem1(get_input()); + let duration = start.elapsed(); + println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64()); + + let start = Instant::now(); + let ans2 = problem2(get_input()); + let duration = start.elapsed(); + println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64()); +} + +// PARSE + +#[derive(Debug, Clone)] +enum EdgeType { + FromPath, + FromSlope, +} + +#[derive(Debug, Clone)] +struct Node { + c: char, + pos: Position, +} + +type Position = (usize, usize); + +#[derive(Clone)] +struct ForestMap { + map: Array2, + indexes: HashMap, + graph: DiGraph, + start: Position, + end: Position, +} + +const ADJACENCIES: [(isize, isize); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)]; + +fn offset_pos(map: &Array2, pos: Position, ofs: (isize, isize)) -> Option { + let new_pos = (pos.0 as isize + ofs.0, pos.1 as isize + ofs.1); + if new_pos.0 >= 0 + && new_pos.0 < map.len_of(Axis(0)) as isize + && new_pos.1 >= 0 + && new_pos.1 < map.len_of(Axis(1)) as isize + { + Some((new_pos.0 as usize, new_pos.1 as usize)) + } else { + None + } +} + +fn adjacent_to(map: &Array2, pos: Position) -> Vec { + ADJACENCIES + .iter() + .filter_map(|ofs| offset_pos(map, pos, *ofs)) + .collect() +} + +impl From> for ForestMap { + fn from(lines: Lines) -> Self { + let rows = lines.map(|line| line.unwrap().chars().collect_vec()).collect_vec(); + let map = Array::from_shape_vec([rows[0].len(), rows.len()], rows.into_iter().flatten().collect_vec()) + .unwrap() + .reversed_axes(); + let start = (map.slice(s![.., 0]).iter().position(|c| *c == '.').unwrap(), 0); + let end = ( + map.slice(s![.., map.len_of(Axis(1)) - 1]) + .iter() + .position(|c| *c == '.') + .unwrap(), + map.len_of(Axis(1)) - 1, + ); + + let mut graph = Graph::default(); + let mut indexes = HashMap::new(); + for (pos, c) in map.indexed_iter() { + if *c != '#' { + indexes.insert(pos, graph.add_node(Node { c: *c, pos })); + } + } + + for (pos, c) in map.indexed_iter() { + match c { + '#' => continue, + '.' => { + adjacent_to(&map, pos).iter().for_each(|adj| { + if indexes.contains_key(&adj) { + graph.add_edge(indexes[&pos], indexes[adj], EdgeType::FromPath); + } + }); + } + '^' => { + if let Some(adj) = offset_pos(&map, pos, (0, -1)) { + if indexes.contains_key(&adj) { + graph.add_edge(indexes[&pos], indexes[&adj], EdgeType::FromSlope); + } + } + } + '>' => { + if let Some(adj) = offset_pos(&map, pos, (1, 0)) { + if indexes.contains_key(&adj) { + graph.add_edge(indexes[&pos], indexes[&adj], EdgeType::FromSlope); + } + } + } + 'v' => { + if let Some(adj) = offset_pos(&map, pos, (0, 1)) { + if indexes.contains_key(&adj) { + graph.add_edge(indexes[&pos], indexes[&adj], EdgeType::FromSlope); + } + } + } + '<' => { + if let Some(adj) = offset_pos(&map, pos, (-1, 0)) { + if indexes.contains_key(&adj) { + graph.add_edge(indexes[&pos], indexes[&adj], EdgeType::FromSlope); + } + } + } + c => panic!("invalid map character {}", c), + } + } + Self { + map, + start, + end, + graph, + indexes, + } + } +} + +impl Debug for ForestMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for y in 0..self.map.len_of(Axis(1)) { + for c in self.map.index_axis(Axis(1), y) { + f.write_char(*c)?; + } + writeln!(f)?; + } + // println!("start: {:?} end: {:?}", self.start, self.end); + // println!("digraph aoc23 {{"); + // for node in self.graph.node_indices() { + // println!( + // " \"{},{}\" -> {}", + // self.graph[node].pos.0, + // self.graph[node].pos.1, + // self.graph + // .neighbors(node) + // .map(|n| format!("\"{},{}\"", self.graph[n].pos.0, self.graph[n].pos.1)) + // .join(",") + // ); + // } + // println!("}}"); + Ok(()) + } +} + +// PROBLEM 1 solution + +fn problem1(input: Lines) -> u64 { + let map = ForestMap::from(input); + println!("{:?}", map); + let paths = all_simple_paths::, _>(&map.graph, map.indexes[&map.start], map.indexes[&map.end], 0, None) + .collect_vec(); + let longest = paths.iter().max_by_key(|path| path.len()).unwrap(); + + longest.len() as u64 - 1 +} + +// PROBLEM 2 solution +fn problem2(input: Lines) -> u64 { + 0 +} + +#[cfg(test)] +mod tests { + use crate::*; + use std::io::Cursor; + + const EXAMPLE: &str = &"#.##################### +#.......#########...### +#######.#########.#.### +###.....#.>.>.###.#.### +###v#####.#v#.###.#.### +###.>...#.#.#.....#...# +###v###.#.#.#########.# +###...#.#.#.......#...# +#####.#.#.#######.#.### +#.....#.#.#.......#...# +#.#####.#.#.#########v# +#.#...#...#...###...>.# +#.#.#v#######v###.###v# +#...#.>.#...>.>.#.###.# +#####v#.#.###v#.#.###.# +#.....#...#...#.#.#...# +#.#########.###.#.#.### +#...###...#...#...#.### +###.###.#.###v#####v### +#...#...#.#.>.>.#.>.### +#.###.###.#.###.#.#v### +#.....###...###...#...# +#####################.#"; + + #[test] + fn problem1_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem1(c.lines()), 94); + } + + #[test] + fn problem2_example() { + let c = Cursor::new(EXAMPLE); + assert_eq!(problem2(c.lines()), 0); + } +}