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