From 579393e8fcf5bec02205830fdcf09eaf793bac01 Mon Sep 17 00:00:00 2001 From: Patrick Auernig Date: Fri, 12 Feb 2021 04:25:49 +0100 Subject: [PATCH] Add solution for 2015 day 09 part 1 --- .gitignore | 1 + 2015/day-09/.gitignore | 3 + 2015/day-09/inputs/puzzle.txt | 28 ++++++++ 2015/day-09/inputs/test.txt | 3 + 2015/day-09/notes.md | 15 ++++ 2015/day-09/src/graph.rs | 128 ++++++++++++++++++++++++++++++++++ 2015/day-09/src/main.rs | 34 +++++++++ 2015/day-09/src/part1.rs | 68 ++++++++++++++++++ 2015/day-09/src/utils.rs | 58 +++++++++++++++ 9 files changed, 338 insertions(+) create mode 100644 .gitignore create mode 100644 2015/day-09/.gitignore create mode 100644 2015/day-09/inputs/puzzle.txt create mode 100644 2015/day-09/inputs/test.txt create mode 100644 2015/day-09/notes.md create mode 100644 2015/day-09/src/graph.rs create mode 100644 2015/day-09/src/main.rs create mode 100644 2015/day-09/src/part1.rs create mode 100644 2015/day-09/src/utils.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..751553b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.bak diff --git a/2015/day-09/.gitignore b/2015/day-09/.gitignore new file mode 100644 index 0000000..fba1994 --- /dev/null +++ b/2015/day-09/.gitignore @@ -0,0 +1,3 @@ +*.dot +*.png +main diff --git a/2015/day-09/inputs/puzzle.txt b/2015/day-09/inputs/puzzle.txt new file mode 100644 index 0000000..9850564 --- /dev/null +++ b/2015/day-09/inputs/puzzle.txt @@ -0,0 +1,28 @@ +Faerun to Norrath = 129 +Faerun to Tristram = 58 +Faerun to AlphaCentauri = 13 +Faerun to Arbre = 24 +Faerun to Snowdin = 60 +Faerun to Tambi = 71 +Faerun to Straylight = 67 +Norrath to Tristram = 142 +Norrath to AlphaCentauri = 15 +Norrath to Arbre = 135 +Norrath to Snowdin = 75 +Norrath to Tambi = 82 +Norrath to Straylight = 54 +Tristram to AlphaCentauri = 118 +Tristram to Arbre = 122 +Tristram to Snowdin = 103 +Tristram to Tambi = 49 +Tristram to Straylight = 97 +AlphaCentauri to Arbre = 116 +AlphaCentauri to Snowdin = 12 +AlphaCentauri to Tambi = 18 +AlphaCentauri to Straylight = 91 +Arbre to Snowdin = 129 +Arbre to Tambi = 53 +Arbre to Straylight = 40 +Snowdin to Tambi = 15 +Snowdin to Straylight = 99 +Tambi to Straylight = 70 diff --git a/2015/day-09/inputs/test.txt b/2015/day-09/inputs/test.txt new file mode 100644 index 0000000..2bceede --- /dev/null +++ b/2015/day-09/inputs/test.txt @@ -0,0 +1,3 @@ +London to Dublin = 464 +London to Belfast = 518 +Dublin to Belfast = 141 diff --git a/2015/day-09/notes.md b/2015/day-09/notes.md new file mode 100644 index 0000000..4144c2c --- /dev/null +++ b/2015/day-09/notes.md @@ -0,0 +1,15 @@ +## Search Algorithm 1 + +`O(n^2)` +Visit all nodes with shortest path + +1. Create a set for visited nodes +2. Set current node to start node +3. From the current node compare all outgoing nodes +4. Select the outgoing node with lowest weight that is not marked as visited +5. If a node was found: + 1. Mark current node as visited + 2. Push next node to stack + 3. Set next node to current node +6. Otherwise: + 1. Return with current stack diff --git a/2015/day-09/src/graph.rs b/2015/day-09/src/graph.rs new file mode 100644 index 0000000..3ed076d --- /dev/null +++ b/2015/day-09/src/graph.rs @@ -0,0 +1,128 @@ +use std::{ + rc::Rc, + cell::RefCell, + fmt::{self, Debug}, + hash::{Hash, Hasher}, +}; + +#[derive(Default)] +pub struct Graph { + pub nodes: Vec>, +} + +impl Graph + where T: Eq + Clone, + W: Copy +{ + pub fn add_edge(&mut self, edge: &(T, T, W)) { + let (a, b, w) = edge; + + let mut node_a = { + match self.nodes.iter().find(|&node| *node == *a) { + Some(n) => n.to_owned(), + None => { + let new_node = Node::new(a.clone()); + self.nodes.push(new_node.clone()); + new_node + } + } + }; + + let mut node_b = { + match self.nodes.iter().find(|&node| *node == *b) { + Some(n) => n.to_owned(), + None => { + let new_node = Node::new(b.clone()); + self.nodes.push(new_node.clone()); + new_node + } + } + }; + + node_a.add_edge(node_b.clone(), *w); + node_b.add_edge(node_a, *w); + } +} + +impl Debug for Graph + where T: Debug, + W: Debug +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Graph {{")?; + for node in &self.nodes { + for (outgoing, weight) in &node.borrow().outgoing { + writeln!(f, " {:?} -- [{:?}] -- {:?}", node.borrow().data, weight, outgoing.borrow().data)?; + } + } + writeln!(f, "}}") + } +} + + +pub struct Node(pub Rc>>); + +impl Node { + pub fn new(data: T) -> Self { + let inner = InnerNode { outgoing: Vec::new(), data }; + Self(Rc::new(RefCell::new(inner))) + } + + pub fn add_edge(&mut self, node: Self, weight: W) { + self.0.borrow_mut().outgoing.push((node, weight)); + } + + pub fn borrow(&self) -> std::cell::Ref<'_, InnerNode> { + self.0.borrow() + } +} + +impl Clone for Node { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Hash for Node + where T: Hash +{ + fn hash(&self, hasher: &mut H) { + let this = self.borrow(); + this.data.hash(hasher); + } +} + +impl PartialEq for Node + where T: PartialEq +{ + fn eq(&self, other: &T) -> bool { + self.0.borrow().data == *other + } +} + +impl Eq for Node + where T: Eq {} + +impl PartialEq for Node + where T: PartialEq +{ + fn eq(&self, other: &Self) -> bool { + self.0.borrow().data == other.0.borrow().data + } +} + +impl Debug for Node + where T: Debug, W: Debug +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Node") + .field("data", &self.0.borrow().data) + .finish() + } +} + + +pub struct InnerNode { + pub data: T, + pub outgoing: Vec<(Node, W)>, +} diff --git a/2015/day-09/src/main.rs b/2015/day-09/src/main.rs new file mode 100644 index 0000000..8b0e791 --- /dev/null +++ b/2015/day-09/src/main.rs @@ -0,0 +1,34 @@ +mod graph; +mod utils; +mod part1; + +type Graph = graph::Graph; +type Node = graph::Node; + +type Location = String; +type Distance = u64; + +const FILE_PATHS: &[&str] = &[ + "inputs/test.txt", + "inputs/puzzle.txt", +]; + +fn main() -> Result<(), Box> { + for file_path in FILE_PATHS { + println!("File: {}", file_path); + let locations = utils::parse_file(file_path)?; + + let mut directions = Graph::default(); + + for location in &locations { + directions.add_edge(location); + } + + utils::write_dot(&directions, file_path)?; + + println!("\tPart 1: {:?}", part1::solve(&directions)); + } + + Ok(()) +} + diff --git a/2015/day-09/src/part1.rs b/2015/day-09/src/part1.rs new file mode 100644 index 0000000..a601216 --- /dev/null +++ b/2015/day-09/src/part1.rs @@ -0,0 +1,68 @@ +use std::collections::HashSet; +use crate::{Distance, Graph, Node}; + +type NodeSet = HashSet; + +fn visit_all_shortest(start_node: &Node) -> Vec<(Node, Distance)> { + let mut visited = NodeSet::new(); + let mut path = Vec::new(); + let mut current_node: Node = start_node.clone(); + + 'search: loop { + let mut edge: Option<(Node, Distance)> = None; + + { + let node = current_node.borrow(); + for (out_node, out_weight) in &node.outgoing { + if visited.contains(&out_node) { + continue; + } + + edge = Some(match edge { + None => (out_node.clone(), *out_weight), + Some((n, w)) => { + if out_weight < &w { + (out_node.clone(), *out_weight) + } else { + (n, w) + } + } + }); + } + } + + match edge { + None => break 'search, + Some((next_node, weight)) => { + visited.insert(current_node.clone()); + path.push((next_node.clone(), weight)); + current_node = next_node; + } + } + } + + path +} + +/// Find shortest path that connects two points and visits all locations. +pub fn solve(directions: &Graph) -> Option { + use std::cmp::min; + + let mut shortest_distance = None; + + for node in &directions.nodes { + let path = visit_all_shortest(node); + + if path.is_empty() { + continue; + } + + let distance = path.iter().map(|p| p.1).sum(); + shortest_distance = match shortest_distance { + None => Some(distance), + Some(d) => Some(min(distance, d)), + }; + } + + shortest_distance +} diff --git a/2015/day-09/src/utils.rs b/2015/day-09/src/utils.rs new file mode 100644 index 0000000..ecca925 --- /dev/null +++ b/2015/day-09/src/utils.rs @@ -0,0 +1,58 @@ +use std::{ + io::{self, BufRead}, + path::{Path, PathBuf}, +}; +use crate::{Location, Distance, Graph}; + +pub fn generate_dot(graph: &Graph, name: &str) -> Result> { + use std::fmt::Write; + + let mut buf = String::new(); + + writeln!(buf, "strict graph {} {{", name)?; + for node in &graph.nodes { + for (outgoing, weight) in &node.borrow().outgoing { + writeln!(buf, "{} -- {} [ label = \"{}\" ];", + node.borrow().data, + outgoing.borrow().data, + weight + )?; + } + } + writeln!(buf, "}}")?; + + Ok(buf) +} + +pub fn write_dot

(graph: &Graph, path: P) -> Result<(), Box> + where P: Into +{ + use std::{fs::OpenOptions, io::Write}; + + let mut dot_path = path.into(); + dot_path.set_extension("dot"); + let graph_name = dot_path.file_stem().unwrap().to_str().unwrap(); + let dot = generate_dot(&graph, &graph_name)?; + let out_path = dot_path.file_name().unwrap(); + let mut file = OpenOptions::new().write(true).truncate(true).create(true).open(out_path)?; + + write!(file, "{}", dot)?; + Ok(()) +} + +pub fn parse_file

(path: P) -> io::Result> + where P: AsRef +{ + use std::{fs::File, io::BufReader}; + + let file = BufReader::new(File::open(path)?); + let dests = file.lines() + .map(|l| { + let line = l.unwrap(); + let words = line.split(' ').collect::>(); + (words[0].to_string(), words[2].to_string(), words[4].parse::().unwrap()) + }) + .collect(); + + Ok(dests) +}