diff --git a/src/main.rs b/src/main.rs index ced342b..d0d684c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod wiremap; use crate::args::Command; use crate::endchannel::channel; use crate::fuel::calculate_fuel; +use crate::orbits::Object; use crate::wiremap::WireMap; use std::cmp::{max,min}; @@ -105,8 +106,26 @@ fn main() { } Command::Orbits(uom) => { - println!("Got orbits."); + println!("Got orbits:"); + uom.show(); println!("Base map has {} orbits.", uom.num_orbits()); + match uom.find_path(&Object::new("YOU"), &Object::new("SAN")) { + None => + println!("There is no path from you to Santa. :("), + Some(path) => { + print!("The path from you to Santa is: "); + let mut path_iter = path.iter().peekable(); + + while let Some(x) = path_iter.next() { + print!("{}", x); + if path_iter.peek().is_some() { + print!(" => "); + } + } + println!(""); + println!(" ... so the number of transfers needed is {}", path.len() - 3); + } + } } } } diff --git a/src/orbits.rs b/src/orbits.rs index 6741806..a99d4b2 100644 --- a/src/orbits.rs +++ b/src/orbits.rs @@ -8,6 +8,16 @@ pub enum Object { Object(String), } +impl Object { + pub fn new(s: &str) -> Object { + if s == "COM" { + Object::CenterOfMass + } else { + Object::Object(s.to_string()) + } + } +} + impl fmt::Display for Object { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -21,11 +31,7 @@ impl FromStr for Object { type Err = (); fn from_str(s: &str) -> Result { - if s == "COM" { - Ok(Object::CenterOfMass) - } else { - Ok(Object::Object(s.to_string())) - } + Ok(Object::new(s)) } } @@ -45,8 +51,12 @@ impl FromStr for UniversalOrbitMap { continue; } let mut splits = nextline.split(')'); - let obj1str = splits.next().unwrap_or_else(|| panic!("Bad orbit line: {}", nextline)); - let obj2str = splits.next().unwrap_or_else(|| panic!("Bad orbit line: {}", nextline)); + let obj1str = splits.next() + .unwrap_or_else(|| panic!("Bad orbit line: {}", nextline)) + .trim(); + let obj2str = splits.next() + .unwrap_or_else(|| panic!("Bad orbit line: {}", nextline)) + .trim(); let obj1 = Object::from_str(&obj1str)?; let obj2 = Object::from_str(&obj2str)?; if let Some(prev) = orbits.get_mut(&obj1) { @@ -113,26 +123,104 @@ impl UniversalOrbitMap { } pub fn num_orbits(&self) -> usize { - let mut result = 0; + let mut search_stack = vec![(Object::CenterOfMass, 0)]; + let mut total = 0; - for obj1 in self.objects().iter() { - for obj2 in self.objects().iter() { - if (obj1 != obj2) && self.indirectly_orbits(obj1, obj2) { - result += 1; + while let Some((nextobj, len)) = search_stack.pop() { + total += len; + + match self.orbits.get(&nextobj) { + None => { } + Some(objs) => { + for obj in objs { + search_stack.push((obj.clone(), len + 1)); + } + } + } + } + + total + } + + pub fn show(&self) { + for (key, values) in self.orbits.iter() { + print!("{} => ", key); + for value in values.iter() { + print!("{} ", value); + } + println!(""); + } + } + + fn path_from_origin(&self, obj: &Object) -> Option> { + let mut search_stack = vec![(Object::CenterOfMass, vec![Object::CenterOfMass])]; + let mut total = 0; + + while let Some((nextobj, mut path)) = search_stack.pop() { + if &nextobj == obj { + path.push(nextobj); + return Some(path); + } + + match self.orbits.get(&nextobj) { + None => { } + Some(objs) => { + path.push(nextobj); + for obj in objs { + search_stack.push((obj.clone(), path.clone())); + } } } } - result + panic!("Can't reach object from origin: {}", obj); } + + pub fn find_path(&self, obj1: &Object, obj2: &Object) -> Option> { + let obj1path = self.path_from_origin(obj1)?; + let obj2path = self.path_from_origin(obj2)?; + + println!("path1: {:?}", obj1path); + println!("path2: {:?}", obj2path); + + let joinpoint = find_join_point(&obj1path, &obj2path)?; + + let path1stub = obj1path.iter().skip_while(|x| x != &&joinpoint); + let path2stub = obj2path.iter().skip_while(|x| x != &&joinpoint); + + let mut result = vec![]; + + for x in path1stub { result.push(x.clone()); } + result.reverse(); + for x in path2stub.skip(1) { result.push(x.clone()); } + println!("result: {:?}", result); + + Some(result) + } +} + +fn find_join_point(list1: &Vec, list2: &Vec) -> Option { + for possible in list1.iter().rev() { + if list2.contains(possible) { + return Some(possible.clone()); + } + } + None } #[test] fn examples() { - let input = "COM)B\nB)C\nC)D\nD)E\nE)F\nB)G\nG)H\nD)I\nE)J\nJ)K\nK)L"; - let map = UniversalOrbitMap::from_str(&input).unwrap(); - assert!(map.orbits(&Object::Object("B".to_string()), &Object::CenterOfMass)); - assert!(map.indirectly_orbits(&Object::Object("B".to_string()), &Object::CenterOfMass)); - assert!(map.indirectly_orbits(&Object::Object("E".to_string()),&Object::CenterOfMass)); + let input1 = "COM)B\nB)C\nC)D\nD)E\nE)F\nB)G\nG)H\nD)I\nE)J\nJ)K\nK)L"; + let map = UniversalOrbitMap::from_str(&input1).unwrap(); + assert!(map.orbits(&Object::new("B"), &Object::CenterOfMass)); + assert!(map.indirectly_orbits(&Object::new("B"), &Object::CenterOfMass)); + assert!(map.indirectly_orbits(&Object::new("E"),&Object::CenterOfMass)); assert_eq!(map.num_orbits(), 42); + let day6_contents = std::fs::read("inputs/day6").unwrap(); + let day6_str = std::str::from_utf8(&day6_contents).unwrap(); + let day6map = UniversalOrbitMap::from_str(&day6_str).unwrap(); + assert_eq!(day6map.num_orbits(), 204521); + let input2 = "COM)B\nB)C\nC)D\nD)E\nE)F\nB)G\nG)H\nD)I\nE)J\nJ)K\nK)L\nK)YOU\nI)SAN"; + let map2 = UniversalOrbitMap::from_str(input2).unwrap(); + assert_eq!(map2.find_path(&Object::new("YOU"), &Object::new("SAN")).unwrap().len(), 7); }