blob: 07a853ee2ebd182b4f66608b2486b9e40b026709 [file] [log] [blame]
//! This example is taken from Advent of Code (Day 7)
//!
//! As input it takes day7.txt, an instruction list. The
//! instructions are connected in a graph and we use topological sort
//! to visit them in dependencies first order.
extern crate petgraph;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use petgraph::{
Incoming,
};
use petgraph::visit::Topo;
use petgraph::graph::{
Graph,
NodeIndex,
};
use petgraph::dot::{Dot, Config};
use petgraph::builder::GraphBuilder;
// this is the scalar we compute by
type K = u16;
use Oper::*;
#[derive(Copy, Clone, Debug, PartialEq)]
enum Oper {
Lit(K),
And,
Or,
Lshift(K),
Rshift(K),
Not,
Propagate, // No-op
Unassigned,
}
impl Oper {
fn input_count(&self) -> usize {
match *self {
Lit(_) => 0,
Not => 1,
And => 2,
Or => 2,
Lshift(_) => 1,
Rshift(_) => 1,
Propagate => 1,
Unassigned => 0,
}
}
/// Given `input_values`, compute the output of this operation.
///
/// **Panics** if `self` is Unassigned or if `input_values` is too short.
fn compute(&self, input_values: &[K]) -> K {
match *self {
Lit(x) => x,
Not => !input_values[0],
And => input_values[0] & input_values[1],
Or => input_values[0] | input_values[1],
Lshift(x) => input_values[0] << x,
Rshift(x) => input_values[0] >> x,
Propagate => input_values[0],
Unassigned => unreachable!(),
}
}
}
type OpGraph<'a> = Graph<(&'a str, Oper, K), ()>;
static INPUT: &'static str = include_str!("day7.txt");
static _INPUT: &'static str = r#"
123 -> x
456 -> y
x AND y -> d
x OR y -> e
x LSHIFT 2 -> f
y RSHIFT 2 -> g
NOT x -> h
NOT y -> i"#;
macro_rules! try_unwrap(
($e:expr => $err:expr) => {
match $e {
Some(_value) => _value,
None => return Err($err),
}
};
);
fn parse_graph(input_text: &str)
-> Result<(HashMap<&str, NodeIndex>, OpGraph), &'static str>
{
let mut g = GraphBuilder::from(OpGraph::with_capacity(0, 0));
let mut part1_words = Vec::new();
for line in input_text.trim().lines() {
if line.trim().is_empty() {
continue;
}
let mut parts = line.split("->");
let part1 = try_unwrap!(parts.next() => "syntax error").trim();
part1_words.clear();
part1_words.extend(part1.split_whitespace());
let part2 = try_unwrap!(parts.next() => "syntax error").trim();
let op;
let mut input_names = ["", ""];
// parse operation and add inputs
if part1_words.len() == 1 {
input_names[0] = part1_words[0];
op = Propagate;
} else if part1_words[0] == "NOT" {
input_names[0] = part1_words[1];
op = Not;
} else if part1_words[1] == "RSHIFT" {
input_names[0] = part1_words[0];
op = Rshift(try_unwrap!(part1_words[2].parse::<K>().ok()
=> "failed to parse Rshift operand"));
} else if part1_words[1] == "LSHIFT" {
input_names[0] = part1_words[0];
op = Lshift(try_unwrap!(part1_words[2].parse::<K>().ok()
=> "failed to parse Lshift operand"));
} else {
input_names[0] = part1_words[0];
input_names[1] = part1_words[2];
op = match part1_words[1] {
"AND" => And,
"OR"=> Or,
_ => return Err("parsing error: unexpected operation"),
};
}
let mut inputs = [NodeIndex::end(), NodeIndex::end()];
let output = {
let mut mknod = |name| {
let name: &str = name;
if let Ok(x) = name.parse::<K>() {
// it's a literal -- make a literal node for it
g.ensure_node(name, (name, Lit(x), 0))
} else {
// make a placeholder node for it
g.ensure_node(name, (name, Unassigned, 0))
}
};
// add input nodes
for i in 0..op.input_count() {
inputs[i] = mknod(input_names[i]);
}
// Add the operation node
mknod(part2)
};
if g[output].1 != Unassigned {
return Err("parsing error: duplicate output");
}
g[output].1 = op;
// Add edges from input to operation
for &input in &inputs[..op.input_count()] {
assert!(input != NodeIndex::end());
g.update_edge(input, output, ());
}
}
Ok(g.into_inner())
}
fn compute_graph(g: &mut OpGraph) {
// use toposort to walk the graph in dependencies first order
let mut topo = Topo::new(g);
while let Some(nx) = topo.next(g) {
let mut input_values = [!0, !0];
for (i, n) in g.neighbors_directed(nx, Incoming).enumerate() {
input_values[i] = g[n].2;
}
let (ref _name, ref oper, ref mut value) = g[nx];
*value = oper.compute(&input_values);
/*
println!("Visited {:?}\t{:?} {:?}",
_name, oper, &input_values[..oper.input_count()]);
*/
}
}
fn compute_part2(g: &mut OpGraph, map: &HashMap<&str, NodeIndex>) {
// take the value in node a, and put it in node b (a literal) and run again
let a_value = g[map["a"]].2;
println!("Value in a is {:?}", a_value);
g[map["b"]].1 = Lit(a_value);
compute_graph(g);
let a_value = g[map["a"]].2;
println!("Value in a is {:?}", a_value);
}
fn main() {
let (map, mut g) = parse_graph(INPUT).unwrap();
compute_graph(&mut g);
let mut output_dot = File::create("day7.dot").unwrap();
writeln!(&mut output_dot, "{:?}",
Dot::with_config(&g, &[Config::EdgeNoLabel])).unwrap();
compute_part2(&mut g, &map);
}