Add solution for 2015 day 09 part 1

This commit is contained in:
Patrick Auernig 2021-02-12 04:25:49 +01:00
parent a2e06dd49c
commit 579393e8fc
9 changed files with 338 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.bak

3
2015/day-09/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.dot
*.png
main

View File

@ -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

View File

@ -0,0 +1,3 @@
London to Dublin = 464
London to Belfast = 518
Dublin to Belfast = 141

15
2015/day-09/notes.md Normal file
View File

@ -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

128
2015/day-09/src/graph.rs Normal file
View File

@ -0,0 +1,128 @@
use std::{
rc::Rc,
cell::RefCell,
fmt::{self, Debug},
hash::{Hash, Hasher},
};
#[derive(Default)]
pub struct Graph<T, W> {
pub nodes: Vec<Node<T, W>>,
}
impl<T, W> Graph<T, W>
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<T, W> Debug for Graph<T, W>
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<T, W>(pub Rc<RefCell<InnerNode<T, W>>>);
impl<T, W> Node<T, W> {
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<T, W>> {
self.0.borrow()
}
}
impl<T, W> Clone for Node<T, W> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T, W> Hash for Node<T, W>
where T: Hash
{
fn hash<H: Hasher>(&self, hasher: &mut H) {
let this = self.borrow();
this.data.hash(hasher);
}
}
impl<T, W> PartialEq<T> for Node<T, W>
where T: PartialEq
{
fn eq(&self, other: &T) -> bool {
self.0.borrow().data == *other
}
}
impl<T, W> Eq for Node<T, W>
where T: Eq {}
impl<T, W> PartialEq for Node<T, W>
where T: PartialEq
{
fn eq(&self, other: &Self) -> bool {
self.0.borrow().data == other.0.borrow().data
}
}
impl<T, W> Debug for Node<T, W>
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<T, W> {
pub data: T,
pub outgoing: Vec<(Node<T, W>, W)>,
}

34
2015/day-09/src/main.rs Normal file
View File

@ -0,0 +1,34 @@
mod graph;
mod utils;
mod part1;
type Graph = graph::Graph<Location, Distance>;
type Node = graph::Node<Location, Distance>;
type Location = String;
type Distance = u64;
const FILE_PATHS: &[&str] = &[
"inputs/test.txt",
"inputs/puzzle.txt",
];
fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

68
2015/day-09/src/part1.rs Normal file
View File

@ -0,0 +1,68 @@
use std::collections::HashSet;
use crate::{Distance, Graph, Node};
type NodeSet = HashSet<Node>;
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<Distance> {
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
}

58
2015/day-09/src/utils.rs Normal file
View File

@ -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<String, Box<dyn std::error::Error>> {
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<P>(graph: &Graph, path: P) -> Result<(), Box<dyn std::error::Error>>
where P: Into<PathBuf>
{
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<P>(path: P) -> io::Result<Vec<(Location, Location, Distance)>>
where P: AsRef<Path>
{
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::<Vec<_>>();
(words[0].to_string(), words[2].to_string(), words[4].parse::<Distance>().unwrap())
})
.collect();
Ok(dests)
}