Add solution for 2015 day 09 part 1
This commit is contained in:
parent
a2e06dd49c
commit
579393e8fc
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.bak
|
3
2015/day-09/.gitignore
vendored
Normal file
3
2015/day-09/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.dot
|
||||||
|
*.png
|
||||||
|
main
|
28
2015/day-09/inputs/puzzle.txt
Normal file
28
2015/day-09/inputs/puzzle.txt
Normal 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
|
3
2015/day-09/inputs/test.txt
Normal file
3
2015/day-09/inputs/test.txt
Normal 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
15
2015/day-09/notes.md
Normal 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
128
2015/day-09/src/graph.rs
Normal 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
34
2015/day-09/src/main.rs
Normal 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
68
2015/day-09/src/part1.rs
Normal 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
58
2015/day-09/src/utils.rs
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user